package com.supermap.server;
import com.supermap.SM2Utils;
import com.supermap.crypto.CryproType;
import com.supermap.crypto.CryptoUtils;
import com.supermap.exception.ServerException;
import com.supermap.protocol.RpcDecoder;
import com.supermap.protocol.RpcEncoder;
import com.supermap.protocol.RpcRequest;
import com.supermap.protocol.RpcResponse;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;

import static com.supermap.server.ServerHandler.getTime;

/*
 * @author liqs
 * @version 1.0
 * @date 2020/12/28 17:58
 * 服务端
 */

public class NettyServer {

    public static String getCryptoTypeName() {
        return cryptoTypeName;
    }

    private static String cryptoTypeName;

    public NettyServer(String inputCryptoTypeName) {
        cryptoTypeName = inputCryptoTypeName;
    }

    public void bind(int port) throws Exception {

        EventLoopGroup bossGroup  = new NioEventLoopGroup();
        EventLoopGroup workerGroup  = new NioEventLoopGroup();
        ServerBootstrap sb = new ServerBootstrap();
        ServerBootstrap serverBootstrap = sb.group(bossGroup, workerGroup);
        ServerBootstrap channel = serverBootstrap.channel(NioServerSocketChannel.class);
        ServerBootstrap option = channel.option(ChannelOption.SO_BACKLOG, 128);
        ServerBootstrap serverBootstrap1 = option.childOption(ChannelOption.SO_KEEPALIVE, true);
        serverBootstrap1.childHandler(new ChannelInitializer<SocketChannel>() {  // 绑定客户端连接时候触发操作
            @Override
            protected void initChannel(SocketChannel sh) throws Exception {
                sh.pipeline()
                        .addLast(new RpcDecoder(RpcRequest.class)) //解码request
                        .addLast(new RpcEncoder(RpcResponse.class)) //编码response
                        .addLast(new ServerHandler()); //使用ServerHandler类来处理接收到的消息
            }
        });

        //绑定监听端口，调用sync同步阻塞方法等待绑定操作完成，完成后返回ChannelFuture类似于JDK中Future
        ChannelFuture future = sb.bind(port).sync();
        if (future.isSuccess()) {
            System.out.println(getTime() + " com.suermap.NettyServer\n" +
                    "INFO: server success start , Listen on port : " + port);
        } else {
            System.out.println(getTime() + " com.suermap.NettyServer\n" +
                    "server start filed");
            future.cause().printStackTrace();
            bossGroup .shutdownGracefully(); //关闭线程组
            workerGroup .shutdownGracefully();
        }
        //成功绑定到端口之后,给channel增加一个 管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程。
        future.channel().closeFuture().sync();
    }

    private static PrivateKey serverPrivateKey;
    private static PublicKey serverPublicKey;
    private static PublicKey clientPublicKey;

    public static byte[] encrypt(String msg) {
        try {
            if (cryptoTypeName == CryproType.getRSA()) {
                return CryptoUtils.rsaEncrypt(msg, clientPublicKey);
            } else if(cryptoTypeName == CryproType.getSM2()) {
                return SM2Utils.encrypt(clientPublicKey, msg.getBytes("utf-8"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(byte[] msg) {
        try {
            if (cryptoTypeName == CryproType.getRSA()) {
                return CryptoUtils.rsaDecrypt(msg, serverPrivateKey);
            } else if(cryptoTypeName == CryproType.getSM2()) {
                return new String(SM2Utils.decrypt(serverPrivateKey, msg));
            }
        } catch (Exception e) {
            ServerException.setException(e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

    public static void loadServerPublicFromPath(String filepath) {
        try {
            File file = new File(filepath);
            if (file.exists()) {
                serverPublicKey = SM2Utils.loadPublicFromFile(filepath);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void loadServerPrivFromPath(String privPath, String pubPath, String cryptoType) {
        File privFile = new File(privPath);
        File pubFile = new File(pubPath);
        try {
            if (cryptoType == CryproType.getRSA()) {
                if (privFile.exists() && pubFile.exists()) {
                    serverPrivateKey  = CryptoUtils.getRSAPrivateKeyFromFile(privPath);
                    serverPublicKey = CryptoUtils.getRSAPublicKeyFromFile(pubPath);
                } else {
                    KeyPair keyPair = CryptoUtils.generateKeyPair(cryptoType, 512);
                    serverPrivateKey = keyPair.getPrivate();
                    serverPublicKey = keyPair.getPublic();
                    CryptoUtils.saveRSAPrivateKeyToFile(serverPrivateKey, privPath);
                    CryptoUtils.saveRSAPublicKeyToFile(serverPublicKey, pubPath);
                }
            } else if (cryptoType == CryproType.getSM2()) {
                if (privFile.exists() && pubFile.exists()) {
                    serverPrivateKey = SM2Utils.loadPrivFromFile(privPath, "");
                    serverPublicKey = SM2Utils.loadPublicFromFile(pubPath);
                } else {
                    generateClientKeyPair();
                    Files.write(Paths.get(privPath), SM2Utils.pemFrom(serverPrivateKey, "").getBytes());
                    Files.write(Paths.get(pubPath), SM2Utils.pemFrom(serverPublicKey).getBytes());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void generateClientKeyPair(){
        try {
            KeyPair keyPair = SM2Utils.generatekeyPair();
            serverPrivateKey = keyPair.getPrivate();
            serverPublicKey = keyPair.getPublic();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static PublicKey getServerPublicKey() {
        return serverPublicKey;
    }

    public static PrivateKey getServerPrivateKey() {
        return serverPrivateKey;
    }

    public static void loadClientPublicKey(PublicKey publicKey) {
        clientPublicKey = publicKey;
    }

}
